/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.skatiger.jrove.tag;

import java.beans.IntrospectionException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.WeakHashMap;

import org.skatiger.jrove.JroveContext;
import org.skatiger.jrove.MetaRule;
import org.skatiger.jrove.MetaRuleset;
import org.skatiger.jrove.Metadata;
import org.skatiger.jrove.util.AssertUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

final class MetaRulesetImpl extends MetaRuleset {

    private final static WeakHashMap<Class<?>, MetadataTarget> metadata = new WeakHashMap<Class<?>, MetadataTarget>();

    private final static Logger logger = LoggerFactory
            .getLogger(MetaRulesetImpl.class);

    private final Tag tag;

    private final Class<?> type;

    private final Map<String, TagAttribute> attributes;

    private final List<Metadata> mappers;

    private final List<MetaRule> rules;

    private final static Metadata NONE = new Metadata() {
        @Override
        public void applyMetadata(JroveContext ctx, Object instance) {
            // do nothing
        }
    };

    public MetaRulesetImpl(Tag tag, Class<?> type) {
        this.tag = tag;
        this.type = type;
        this.attributes = new HashMap<String, TagAttribute>();
        this.mappers = new ArrayList<Metadata>();
        this.rules = new ArrayList<MetaRule>();

        // setup attributes
        TagAttribute[] attrs = this.tag.getAttributes().getAll();
        for (int i = 0; i < attrs.length; i++) {
            attributes.put(attrs[i].getLocalName(), attrs[i]);
        }

        // add default rules
        this.rules.add(BeanPropertyTagRule.Instance);
    }

    @Override
    public MetaRuleset add(Metadata mapper) {
        AssertUtil.notNull(mapper);
        if (!this.mappers.contains(mapper)) {
            this.mappers.add(mapper);
        }
        return this;
    }

    @Override
    public MetaRuleset addRule(MetaRule rule) {
        AssertUtil.notNull(rule);
        this.rules.add(rule);
        return this;
    }

    @Override
    public MetaRuleset alias(String attribute, String property) {
        AssertUtil.notNull(attribute);
        AssertUtil.notNull(property);
        TagAttribute attr = (TagAttribute) this.attributes.remove(attribute);
        if (attr != null) {
            this.attributes.put(property, attr);
        }
        return this;
    }

    @Override
    public Metadata finish() {
        if (!this.attributes.isEmpty()) {
            if (this.rules.isEmpty()) {
                if (logger.isErrorEnabled()) {
                    for (Iterator<TagAttribute> itr = this.attributes.values()
                            .iterator(); itr.hasNext();) {
                        logger.error(itr.next()
                                + " Unhandled by MetaTagHandler for type "
                                + this.type.getName());
                    }
                }
            } else {
                MetadataTarget target = this.getMetadataTarget();
                // now iterate over attributes
                int ruleEnd = this.rules.size() - 1;
                for (Iterator<Entry<String, TagAttribute>> itr = this.attributes
                        .entrySet().iterator(); itr.hasNext();) {
                    Map.Entry<String, TagAttribute> entry = itr.next();
                    Metadata data = null;
                    int i = ruleEnd; // 反向循环
                    while (data == null && i >= 0) {
                        MetaRule rule = this.rules.get(i);
                        data = rule.applyRule(entry.getKey(), entry.getValue(),
                                target);
                        i--;
                    }
                    if (data == null) {
                        logger.error(entry.getValue()
                                + " Unhandled by MetaTagHandler for type "
                                + this.type.getName());
                    } else {
                        this.mappers.add(data);
                    }
                }

            }
        }
        if (this.mappers.isEmpty()) {
            return NONE;
        } else {
            return new MetadataImpl(
                    this.mappers.toArray(new Metadata[this.mappers.size()]));
        }
    }

    private final MetadataTarget getMetadataTarget() {

        MetadataTarget meta = (MetadataTarget) metadata.get(type);
        if (meta == null) {
            try {
                meta = new MetadataTargetImpl(type);
            } catch (IntrospectionException e) {
                throw new TagException(this.tag,
                        "Error Creating TargetMetadata", e);
            }
            metadata.put(type, meta);
        }
        return meta;
    }

    @Override
    public MetaRuleset ignore(String attribute) {
        AssertUtil.notNull("attribute", attribute);
        this.attributes.remove(attribute);
        return this;
    }

    @Override
    public MetaRuleset ignoreAll() {
        this.attributes.clear();
        return this;
    }
}
